package com.hama.iotrust.common.mybatis.criteria;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.util.StringUtils;

import com.hama.iotrust.common.Constants;
import com.hama.iotrust.common.mybatis.criteria.Criteria;
import com.hama.iotrust.common.mybatis.criteria.CriteriaException;
import com.hama.iotrust.common.mybatis.criteria.Expression;
import com.hama.iotrust.common.mybatis.criteria.ExpressionFactory;
import com.hama.iotrust.common.mybatis.criteria.SimpleExpression;
import com.hama.iotrust.common.mybatis.criteria.Sort;
import com.hama.iotrust.common.mybatis.criteria.SimpleExpression.Operator;
import com.hama.iotrust.common.util.SpringContextHolder;

/**
 * 复合查询条件及分页<br/>
 * 拦截 mybatis 查询时会自动进行 property - column 的转换<br/>
 * 分页默认不启用，<code>setEnablePagination(true)</code> 启用分页
 *
 * @param <T> 查询条件对应的实体类
 * @author 金建强(ptma@163.com)
 * @version 1.0.0
 * @since 2013-3-25 11:23:16
 */
public class Criteria<T> {

    private static final String RESULT_MAP_POSTFIX   = "ResultMap";

    private Class<T>            m_entityClass;
    private List<Expression>    allCriteria          = new ArrayList<Expression>();
    private List<Sort>          allSort              = new ArrayList<Sort>();
    private MetaClass           m_metaClass;
    private Configuration       m_configuration;
    private boolean             propertiesMapBuilded = false;
    private Map<String, String> m_propertiesMap      = new HashMap<String, String>();

    public static final String  PARAM_PREFIX         = "params.";
    Map<String, Object>         params               = new HashMap<String, Object>(); // 参数和值

    private int                 pageSize             = Constants.DEFAULT_PAGESIZE;   // 每页显示记录数
    private int                 offset               = 0;
    private int                 totalPages           = 1;                            // 总页数
    private int                 totalRecords         = 0;                            // 总记录数
    private int                 currentPage          = 1;                            // 当前页
    private boolean             enablePagination     = false;                         // 分页启用开关

    private int                 expParamCount        = 1;                             // 表达式参数计数器，以保证多次查询时参数占位符后缀一致

    private Criteria(Class<T> entity){
        m_entityClass = entity;
        m_metaClass = MetaClass.forClass(m_entityClass);
    }

    /**
     * 创建查询条件实体
     *
     * @param <T>
     * @param entity 条件对应的实体
     * @return
     */
    public static synchronized <T> Criteria<T> create(Class<T> entity) {
        Criteria<T> criteria = new Criteria<T>(entity);
        SqlSessionFactory sqlSessionFactory = SpringContextHolder.getBean(SqlSessionFactory.class);
        if (null != sqlSessionFactory) criteria.bind(sqlSessionFactory.getConfiguration());
        return criteria;
    }

    /**
     * 从 Request 自动处理前端由EasyUI DataGrid传递过来的分页、排序参数,拼装查询 约定：查询条件的自动处理需要满足如下条件： 1、 参数名为 Criteria 所绑定的实体类中的属性名； 2、
     * 查询条件默认为=表达式，如果要针对某一属性进行其它表达式查询时，需要同时提供一个参数 属性名_op=操作符 ， 合法的操作符有如下6个： <，>，<=，>=，<>，like
     *
     * @param request
     * @return
     */
    public Criteria<T> buildFromRequest(HttpServletRequest request) {
        return buildFromRequest(request, new String[] {});
    }

    public Criteria<T> buildFromRequest(HttpServletRequest request, String... excludesProperty) {
        String sortProperty = null, order = null;
        Set<String> excludesProperties = new HashSet<String>();
        if (null != excludesProperty) {
            excludesProperties.addAll(Arrays.asList(excludesProperty));
        }
        for (Enumeration<String> e = request.getParameterNames(); e.hasMoreElements();) {
            String property = e.nextElement().toString();
            String value = request.getParameter(property);
            if (excludesProperties.contains(property)) {
                continue;
            }
            if (!StringUtils.isEmpty(value)) {
                if ("page".equals(property)) {
                    setCurrentPage(Integer.parseInt(value));
                } else if ("rows".equals(property)) {
                    setPageSize(Integer.parseInt(value));
                } else if ("sort".equals(property)) {
                    sortProperty = value;
                } else if ("order".equals(property)) {
                    order = value;
                } else if (hasProperty(property)) {
                    String op = request.getParameter(property + "_op");
                    // <，>，<=，>=，<>，like
                    if (!StringUtils.isEmpty(op)) {
                        Operator operator = Operator.fromString(op);
                        if (operator != null) {
                            add(new SimpleExpression(property, operator, value));
                        } else if ("like".equals(op)) {
                            add(ExpressionFactory.like(property, "%" + value + "%"));
                        } else {
                            add(ExpressionFactory.eq(property, value));
                        }
                    } else add(ExpressionFactory.eq(property, value));
                }
            }
        }
        if (!StringUtils.isEmpty(sortProperty)) {
            if ("desc".equals(order)) {
                this.sort(Sort.desc(sortProperty));
            } else {
                this.sort(Sort.asc(sortProperty));
            }
        }
        return this;
    }

    /**
     * 添加一个表达式
     *
     * @param crit
     * @return
     */
    public Criteria<T> add(Expression crit) {
        allCriteria.add(crit);
        return this;
    }

    /**
     * 添加一个排序
     *
     * @param crit
     * @return
     */
    public Criteria<T> sort(Sort sort) {
        allSort.add(sort);
        return this;
    }

    /**
     * 添加默认排序，排序集为空时有效
     *
     * @param sort
     * @return
     */
    public Criteria<T> sortIfEmpty(Sort sort) {
        if (allSort.isEmpty()) allSort.add(sort);
        return this;
    }

    public boolean isSortEmpty() {
        return allSort.isEmpty();
    }

    /**
     * 是否启用分页
     */
    public Criteria<T> pagination(boolean value) {
        this.setEnablePagination(value);
        return this;
    }

    /**
     * 设置当前页码
     */
    public Criteria<T> currentPage(int currentPage) {
        this.setCurrentPage(currentPage);
        return this;
    }

    /**
     * 设置分页每页记录数
     */
    public Criteria<T> pageSize(int pageSize) {
        this.setPageSize(pageSize);
        return this;
    }

    /**
     * 校验所有表达式中的属性名和值是否合法
     */
    public boolean verify() {
        for (Expression crit : allCriteria) {
            if (!crit.verify(this)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 依据 MyBatis mapper 配置来绑定属性名到字段名
     *
     * @throws 存在任意一个属性名不能绑定到字段名时抛出 CriteriaException.
     */
    public Criteria<T> bind(Configuration conf) throws CriteriaException {
        m_configuration = conf;
        return this;
    }

    /**
     * 得到使用占位符的 sql 子句 ， 用于 myBatis mapper 文件
     */
    public List<Expression> getAllCriteria() {
        if (m_configuration != null) {
            buildAttributeMap();
            for (Expression crit : allCriteria) {
                crit.bind(this);
                params.putAll(crit.getParameter());
            }
        }
        return allCriteria;
    }

    /**
     * 获取排序集
     */
    public List<Sort> getAllSort() {
        if (m_configuration != null) {
            buildAttributeMap();
            for (Sort sort : allSort) {
                sort.bind(this);
            }
        }
        return allSort;
    }

    public Map<String, Object> getParams() {
        getAllCriteria();
        getAllSort();
        return params;
    }

    /**
     * Checks for property.
     *
     * @param propName the prop name
     * @return true, if successful
     */
    boolean hasProperty(String propName) {
        return m_metaClass.hasGetter(propName);
    }

    protected ResultMap findMapForType() {
        if (m_configuration == null) {
            throw new CriteriaException(
                                        String.format("尚未绑定 org.apache.ibatis.session.Configuration, 不能从实体类  [%s] 查找映射",
                                                      m_entityClass));
        }
        Collection<?> resultMaps = m_configuration.getResultMaps();
        for (Object item : resultMaps) {
            if (item instanceof ResultMap) {
                ResultMap rm = (ResultMap) item;
                if (rm.getType().equals(m_entityClass) && rm.getId().endsWith(RESULT_MAP_POSTFIX)) {
                    return rm;
                }
            }
        }
        throw new CriteriaException(String.format("不能从实体类  [%s] 查找映射", m_entityClass));
    }

    /**
     * 返回绑定实体相应属性(Property)对应的数据库字段名(Column)，否则会抛出 CriteriaException 异常
     */
    protected String toColumnName(String propertyName) {
        String colName = m_propertiesMap.get(propertyName);
        if (colName == null) {
            throw new CriteriaException(String.format("不能映射属性  [%s] 到数据库字段", propertyName));
        }

        return colName;
    }

    /**
     * 得到 property - column 映射表，用于 toColumnName 方法;
     */
    private void buildAttributeMap() {
        if (propertiesMapBuilded) return;
        ResultMap rm = findMapForType();
        for (ResultMapping singleMapping : rm.getResultMappings()) {
            m_propertiesMap.put(singleMapping.getProperty(), singleMapping.getColumn());
        }
        propertiesMapBuilded = true;
    }

    /**
     * 检查指定Class是否包含某个属性
     *
     * @param propName 要检查的属性名
     * @param clazz 被检查的类
     * @return clazz 包含 propName 时返回 true
     */
    boolean checkType(String propName, Class<?> clazz) {
        Class<?> getterType = m_metaClass.getGetterType(propName);
        if (getterType.isPrimitive()) {
            if (getterType.equals(Integer.TYPE)) {
                getterType = Integer.class;
            }
            if (getterType.equals(Boolean.TYPE)) {
                getterType = Boolean.class;
            }
            if (getterType.equals(Character.TYPE)) {
                getterType = Character.class;
            }
            if (getterType.equals(Byte.TYPE)) {
                getterType = Byte.class;
            }
            if (getterType.equals(Short.TYPE)) {
                getterType = Short.class;
            }
            if (getterType.equals(Long.TYPE)) {
                getterType = Long.class;
            }
            if (getterType.equals(Float.TYPE)) {
                getterType = Float.class;
            }
            if (getterType.equals(Double.TYPE)) {
                getterType = Double.class;
            }
            if (getterType.equals(Void.TYPE)) {
                getterType = Void.class;
            }
        }
        return getterType.isAssignableFrom(clazz);
    }

    /**
     * 得到总页数
     *
     * @return
     */
    public int getTotalPages() {
        if (totalRecords % pageSize == 0) totalPages = totalRecords / pageSize;
        else totalPages = totalRecords / pageSize + 1;
        return totalPages;
    }

    /**
     * 设置总页数
     *
     * @param totalPage
     */
    public void setTotalPages(int totalPages) {
        this.totalPages = totalPages;
    }

    /**
     * 得到记录总数
     *
     * @return
     */
    public int getTotalRecords() {
        return totalRecords;
    }

    /**
     * 设置记录总数
     *
     * @param totalResult
     */
    public void setTotalRecords(int totalRecords) {
        this.totalRecords = totalRecords;
        getTotalPages();
    }

    /**
     * 得到当前页码
     *
     * @return
     */
    public int getCurrentPage() {
        if (currentPage <= 0) currentPage = 1;
        int pageCount = getTotalPages();
        if (pageCount > 0 && currentPage > getTotalPages()) currentPage = pageCount;
        return currentPage;
    }

    /**
     * 设置当前页码
     *
     * @param currentPage
     */
    public Criteria<T> setCurrentPage(int currentPage) {
        this.currentPage = currentPage;
        if (this.currentPage <= 0) this.currentPage = 1;
        return this;
    }

    /**
     * 得到分页每页记录数
     *
     * @return
     */
    public int getPageSize() {
        return pageSize;
    }

    /**
     * 设置分页每页记录数
     *
     * @param pageSize
     */
    public Criteria<T> setPageSize(int pageSize) {
        this.pageSize = pageSize;
        return this;
    }

    /**
     * 当前是否已启用分页
     *
     * @return true/false
     */
    public boolean isEnablePagination() {
        return enablePagination;
    }

    /**
     * 设置是否已启用分页
     *
     * @param enablePagination true/false
     */
    public void setEnablePagination(boolean enablePagination) {
        this.enablePagination = enablePagination;
    }

    /**
     * 获取下一个占位符后缀
     *
     * @return
     */
    public String nextParamSuffix() {
        return "crit" + expParamCount++;
    }

    public RowBounds getRowBounds() {
        int offset = (getCurrentPage() - 1) * getPageSize();
        int limit = getPageSize();
        return new RowBounds(offset, limit);
    }

    /**
     * 记录偏移
     *
     * @return
     */
    public int getOffset() {
        offset = (getCurrentPage() - 1) * getPageSize();
        return offset;
    }
}
